import {Layout} from '../../src/Layout';
export default Layout;

import docs from 'docs:react-aria-components';
import vanillaDocs from 'docs:vanilla-starter/Calendar';
import {Calendar as VanillaCalendar} from 'vanilla-starter/Calendar';
import {Calendar as TailwindCalendar} from 'tailwind-starter/Calendar';
import '../../tailwind/tailwind.css';
import Anatomy from '@react-aria/calendar/docs/calendar-anatomy.svg';

export const tags = ['date'];
export const relatedPages = [{'title': 'useCalendar', 'url': 'Calendar/useCalendar.html'}];
export const description = 'Displays one or more date grids and allows users to select a single date.';

# Calendar

<PageDescription>{docs.exports.Calendar.description}</PageDescription>

<ExampleSwitcher>
  <VisualExample
    component={VanillaCalendar}
    docs={vanillaDocs.exports.Calendar}
    links={vanillaDocs.links}
    props={['isDisabled']}
    type="vanilla"
    files={["starters/docs/src/Calendar.tsx", "starters/docs/src/Calendar.css"]} />
  <VisualExample
    component={TailwindCalendar}
    docs={vanillaDocs.exports.Calendar}
    links={vanillaDocs.links}
    props={['isDisabled']}
    type="tailwind"
    files={["starters/tailwind/src/Calendar.tsx"]} />
</ExampleSwitcher>

## Value

Use the `value` or `defaultValue` prop to set the date value, using objects in the [@internationalized/date](internationalized/date/) package. This library supports parsing date strings in multiple formats, manipulation across international calendar systems, time zones, etc.

```tsx render
"use client";
import {parseDate, getLocalTimeZone} from '@internationalized/date';
import {useDateFormatter} from 'react-aria';
import {Calendar} from 'vanilla-starter/Calendar';
import {useState} from 'react';

function Example() {
  let [date, setDate] = useState(parseDate('2020-02-03'));
  let formatter = useDateFormatter({ dateStyle: 'full' });

  return (
    <>
      <Calendar
        ///- begin highlight -///
        value={date}
        onChange={setDate}
        ///- end highlight -///
      />
      <p>Selected date: {formatter.format(date.toDate(getLocalTimeZone()))}</p>
    </>
  );
}
```

### International calendars

By default, `Calendar` displays the value using the calendar system for the user's locale. Use `<I18nProvider>` to override the calendar system by setting the [Unicode calendar locale extension](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar#adding_a_calendar_in_the_locale_string). The `onChange` event always receives a date in the same calendar as the `value` or `defaultValue` (Gregorian if no value is provided), regardless of the displayed locale.

```tsx render wide docs={docs.exports.I18nProvider} links={docs.links} props={['locale']} initialProps={{locale: 'hi-IN-u-ca-indian'}}
"use client";
import {I18nProvider} from 'react-aria-components';
import {parseDate} from '@internationalized/date';
import {Calendar} from 'vanilla-starter/Calendar';

<I18nProvider/* PROPS */>
  <Calendar defaultValue={parseDate('2025-02-03')} />
</I18nProvider>
```

### Custom calendar systems

`Calendar` also supports custom calendar systems that implement custom business rules, for example a fiscal year calendar that follows a [4-5-4 format](https://nrf.com/resources/4-5-4-calendar), where month ranges don't follow the usual Gregorian calendar. See the [@internationalized/date docs](internationalized/date/Calendar#custom-calendars) for an example implementation.

```tsx render
"use client";
import type {AnyCalendarDate} from '@internationalized/date';
import {CalendarDate, startOfWeek, toCalendar, GregorianCalendar} from '@internationalized/date';
import {Calendar} from 'vanilla-starter/Calendar';

export default function Example() {
  return (
    <Calendar
      firstDayOfWeek="sun"
      ///- begin highlight -///
      createCalendar={() => new Custom454()} />
      ///- end highlight -///
  );
}

// See @internationalized/date docs linked above.
///- begin collapse -///
class Custom454 extends GregorianCalendar {
  weekPattern = [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4];
  getDaysInMonth(date) {
    return this.weekPattern[date.month - 1] * 7;
  }

  fromJulianDay(jd: number): CalendarDate {
    let gregorian = super.fromJulianDay(jd);

    let monthStart = startOfWeek(new CalendarDate(gregorian.year, 1, 1), 'en');
    for (let months = 0; months < this.weekPattern.length; months++) {
      let weeksInMonth = this.weekPattern[months];
      let monthEnd = monthStart.add({weeks: weeksInMonth});
      if (monthEnd.compare(gregorian) > 0) {
        let days = gregorian.compare(monthStart);
        return new CalendarDate(this, monthStart.year, months + 1, days + 1);
      }
      monthStart = monthEnd;
    }

    throw Error('Date is not in any month somehow!');
  }

  toJulianDay(date: AnyCalendarDate): number {
    let monthStart = startOfWeek(new CalendarDate(date.year, 1, 1), 'en');
    for (let month = 1; month < date.month; month++) {
      monthStart = monthStart.add({weeks: this.weekPattern[month - 1]});
    }

    let gregorian = monthStart.add({days: date.day - 1});
    return super.toJulianDay(gregorian);
  }

  getFormattableMonth(date) {
    let gregorian = toCalendar(date, new GregorianCalendar());
    return gregorian.set({month: date.month, day: 1});
  }

  isEqual(other) {
    return other instanceof Custom454;
  }
}
///- end collapse -///
```

## Validation

Use the `minValue` and `maxValue` props to set the valid date range. The `isDateUnavailable` callback prevents certain dates from being selected. For custom validation rules, set the `isInvalid` prop and the `errorMessage` slot.

```tsx render docs={vanillaDocs.exports.Calendar} links={docs.links} props={['isInvalid', 'errorMessage']} wide
"use client";
import {isWeekend, today, getLocalTimeZone} from '@internationalized/date';
import {useLocale} from 'react-aria-components';
import {Calendar} from 'vanilla-starter/Calendar';

function Example(props) {
  let {locale} = useLocale();
  let now = today(getLocalTimeZone());
  let disabledRanges = [
    [now, now.add({ days: 5 })],
    [now.add({ days: 14 }), now.add({ days: 16 })],
    [now.add({ days: 23 }), now.add({ days: 24 })]
  ];

  return (
    <Calendar
      {...props}
      aria-label="Appointment date"
      ///- begin highlight -///
      /* PROPS */
      minValue={today(getLocalTimeZone())}
      isDateUnavailable={date => (
        isWeekend(date, locale) ||
        disabledRanges.some((interval) =>
          date.compare(interval[0]) >= 0 && date.compare(interval[1]) <= 0
        )
      )} />
      ///- end highlight -///
  );
}
```

## Display options

Set the `visibleDuration` prop and render multiple `CalendarGrid` elements to display more than one month at a time. The `pageBehavior` prop controls whether pagination advances by a single month or multiple. The `firstDayOfWeek` prop overrides the locale-specified first day of the week.

```tsx render docs={docs.exports.Calendar} links={docs.links} props={['visibleDuration', 'pageBehavior', 'firstDayOfWeek']} initialProps={{visibleDuration: {months: 2}}} wide
"use client";
import {Calendar, Heading} from 'react-aria-components';
import {CalendarGrid, CalendarCell} from 'vanilla-starter/Calendar';
import {Button} from 'vanilla-starter/Button';
import {useDateFormatter} from 'react-aria';
import {ChevronLeft, ChevronRight} from 'lucide-react';

// TODO: move this into the starter example.
function Example(props) {
  let monthFormatter = useDateFormatter({
    month: 'long',
    year: 'numeric',
    timeZone: 'UTC'
  });

  return (
    <Calendar
      {...props}
      aria-label="Appointment date"
      ///- begin highlight -///
      /* PROPS */
      ///- end highlight -///
      style={{display: 'flex', gap: 12, overflow: 'auto'}}
    >
      {({state}) => (
        [...Array(props.visibleDuration.months).keys()].map(i => (
          <div key={i} style={{flex: 1}}>
            <header style={{minHeight: 32}}>
              {i === 0 &&
                <Button slot="previous" variant="quiet">
                  <ChevronLeft />
                </Button>
              }
              <Heading>{monthFormatter.format(state.visibleRange.start.add({months: i}).toDate(state.timeZone))}</Heading>
              {i === props.visibleDuration.months - 1 &&
                <Button slot="next" variant="quiet">
                  <ChevronRight />
                </Button>
              }
            </header>
            <CalendarGrid offset={{months: i}}>
              {date => <CalendarCell date={date} />}
            </CalendarGrid>
          </div>
        ))
      )}
    </Calendar>
  );
}
```

## Controlling the focused date

Use the `focusedValue` or `defaultFocusedValue` prop to control which date is focused. This controls which month is visible. The `onFocusChange` event is called when a date is focused by the user.

```tsx render
"use client";
import {Calendar} from 'vanilla-starter/Calendar';
import {Button} from 'vanilla-starter/Button';
import {CalendarDate, today, getLocalTimeZone} from '@internationalized/date';
import {useState} from 'react';

function Example() {
  let defaultDate = new CalendarDate(2021, 7, 1);
  let [focusedDate, setFocusedDate] = useState(defaultDate);

  return (
    <div>
      <Button
        style={{marginBottom: 20}}
        onPress={() => setFocusedDate(today(getLocalTimeZone()))}>
        Today
      </Button>
      <Calendar
        ///- begin highlight -///
        focusedValue={focusedDate}
        onFocusChange={setFocusedDate}
        ///- end highlight -///
      />
    </div>
  );
}
```

### Month and year pickers

You can also control the focused date via `CalendarStateContext`. This example shows month and year dropdown components that work inside any `<Calendar>`.

```tsx render files={['packages/dev/s2-docs/pages/react-aria/MonthDropdown.tsx', 'packages/dev/s2-docs/pages/react-aria/YearDropdown.tsx']}
"use client";
import {Calendar} from 'react-aria-components';
import {CalendarGrid, CalendarCell} from 'vanilla-starter/Calendar';
import {MonthDropdown} from './MonthDropdown';
import {YearDropdown} from './YearDropdown';
import {Button} from 'vanilla-starter/Button';
import {ChevronLeft, ChevronRight} from 'lucide-react';

<Calendar>
  <header style={{display: 'flex', gap: 4}}>
    <Button slot="previous" variant="quiet">
      <ChevronLeft />
    </Button>
    {/*- begin highlight -*/}
    <MonthDropdown />
    <YearDropdown />
    {/*- end highlight -*/}
    <Button slot="next" variant="quiet">
      <ChevronRight />
    </Button>
  </header>
  <CalendarGrid>
    {(date) => <CalendarCell date={date} />}
  </CalendarGrid>
</Calendar>
```

## API

<Anatomy
  role="img"
  aria-label="Anatomy diagram of a calendar component, which consists of a heading, grid of cells, previous, and next buttons." />

```tsx links={{Calendar: '#calendar', Button: 'Button', CalendarGrid: '#calendargrid', CalendarGridHeader: '#calendargridheader', CalendarHeaderCell: '#calendarheadercell', CalendarGridBody: '#calendargridbody', CalendarCell: '#calendarcell'}}
<Calendar>
  <Button slot="previous" />
  <Heading />
  <Button slot="next" />
  <CalendarGrid>
    <CalendarGridHeader>
      {day => <CalendarHeaderCell />}
    </CalendarGridHeader>
    <CalendarGridBody>
      {date => <CalendarCell date={date} />}
    </CalendarGridBody>
  </CalendarGrid>
  <Text slot="errorMessage" />
</Calendar>
```

### Calendar

<PropTable component={docs.exports.Calendar} links={docs.links} showDescription />

### CalendarGrid

<PropTable component={docs.exports.CalendarGrid} links={docs.links} showDescription />

### CalendarGridHeader

<PropTable component={docs.exports.CalendarGridHeader} links={docs.links} showDescription />

### CalendarHeaderCell

<PropTable component={docs.exports.CalendarHeaderCell} links={docs.links} showDescription />

### CalendarGridBody

<PropTable component={docs.exports.CalendarGridBody} links={docs.links} showDescription />

### CalendarCell

<PropTable component={docs.exports.CalendarCell} links={docs.links} showDescription />
